package sshserver

import (
	"awesomeProject/goAgentServer/db"
	"awesomeProject/goAgentServer/encryption"
	"fmt"
	"github.com/gliderlabs/ssh"
	cryptoSSH "golang.org/x/crypto/ssh"
	"io"
	"log"
	"net"
	"os"
	"strconv"
	"strings"
	"time"
)

func sshHandle(s ssh.Session) {
	defer func() {
		io.WriteString(s, fmt.Sprintf("\u001B[1;36;40mgoodbye %s.\u001B[0m\n", s.User()))
		log.Println("jumpserver logout ok,username:", s.User())
		s.Close()
	}()

	for {
		userInfo := db.QueryUserInfo(s.User())

		var u users
		u.GroupIDs = userInfo.GroupIDs
		u.Username = userInfo.Username
		allGroups := userInfo.AllGroups

		if allGroups == 1 {
			var newHostsString string
			groups := db.QueryGroupsAllInfo()

			for _, v := range groups {
				if "" == newHostsString {
					newHostsString = strconv.Itoa(int(v.Id))
				} else {
					newHostsString += fmt.Sprintf(":%v", strconv.Itoa(int(v.Id)))
				}
			}
			u.GroupIDs = newHostsString
		}

		info := u.cacheHostsInfo()

		io.WriteString(s, fmt.Sprintf("\u001B[1;36;40mhello %s, welcome to jump servers.\u001B[0m\n", s.User()))
		io.WriteString(s, fmt.Sprintf("\u001B[1;36;40mEnter \"quit\" or \"exit\" to exit the jump server.\u001B[0m\n\n"))

		for k, v := range info {
			io.WriteString(s, fmt.Sprintf("\u001B[1;33;40m%d : %s\n\u001B[0m", k+1, v.names))
		}

		groupID := readUserInputInfo(s)

		if groupID == -1 {
			break
		}
		if groupID == -2 {
			continue
		}
		groupID = groupID - 1

		if groupID > len(info)-1 {
			io.WriteString(s, fmt.Sprintf("\u001B[1;31;40mGroup does not exist\n\u001B[0m"))
			continue
		}

		for k, v := range info[groupID].hosts {
			//io.WriteString(s, fmt.Sprintf("%d : %s %s\n", k, v.Ip, v.Remask))
			io.WriteString(s, fmt.Sprintf("\u001B[1;33;40m%d : %s %s\n\u001B[0m", k+1, v.Ip, v.Remask))
		}
		//io.WriteString(s, fmt.Sprintf("输入序号ID回车即可连接机器(q|quit退出)\n"))

		hostID := readUserInputInfo(s)
		if hostID == -1 {
			break
		}
		if hostID == -2 {
			continue
		}

		hostID = hostID - 1
		if hostID > len(info[groupID].hosts)-1 {
			io.WriteString(s, fmt.Sprintf("\u001B[1;31;40mHosts does not exist\n\u001B[0m"))
			continue
		}

		io.WriteString(s, fmt.Sprintf("\u001B[1;36;40mThe connection server information is: %v\n\u001B[0m", info[groupID].hosts[hostID]))

		connectServers(s, info[groupID].hosts[hostID].Id)

		//io.WriteString(s, "\u001B[1;31;40mPlease press ctrl+c to continue\n\u001B[0m")
		buf := make([]byte, 1)
		_, err := s.Read(buf)
		if err != nil {
			log.Println("from stdin read data errors ", err)
			break
		}
	}
}

func connectServers(s ssh.Session, hostID uint) {
	//return
	hostInfo, err := db.QueryHostsInfo(hostID)
	if err != nil {
		log.Println("hosts find errors ", err)
		return
	}

	ptyReq, winCh, isPty := s.Pty()
	if !isPty {
		s.Exit(1)
		return
	}
	var conn *cryptoSSH.Client
	var singer cryptoSSH.Signer

	if len(hostInfo.Key) == 0 {
		password, err := encryption.Decrypt(hostInfo.Password)
		if err != nil {
			log.Println("encryption.Decrypt hostInfo.Password error ,err: ", err)
			return
		}
		conn, err = cryptoSSH.Dial("tcp", hostInfo.Addr, &cryptoSSH.ClientConfig{
			User:            hostInfo.Hostname,
			Auth:            []cryptoSSH.AuthMethod{cryptoSSH.Password(password)},
			HostKeyCallback: func(hostname string, remote net.Addr, key cryptoSSH.PublicKey) error { return nil },
		})
		if err != nil {
			io.WriteString(s, fmt.Sprintf("\u001B[1;31;40m%s\n\u001B[0m", err.Error()))

			return
		}
		//log.Println(s.User(), "from password connect hostid:", hostInfo.Id, "ip:", hostInfo.Addr)
	} else {
		keys, err := encryption.Decrypt(hostInfo.Key)
		if err != nil {
			log.Println("encryption.Decrypt hostInfo.Key error ,err: ", err)
			return
		}
		singer, err = cryptoSSH.ParsePrivateKey([]byte(keys))
		if err != nil {
			log.Println("ssh parse private key error ", err)
			return
		}
		conn, err = cryptoSSH.Dial("tcp", hostInfo.Addr, &cryptoSSH.ClientConfig{
			User:            hostInfo.Hostname,
			Auth:            []cryptoSSH.AuthMethod{cryptoSSH.PublicKeys(singer)},
			HostKeyCallback: func(hostname string, remote net.Addr, key cryptoSSH.PublicKey) error { return nil },
		})
		if err != nil {
			io.WriteString(s, fmt.Sprintf("\u001B[1;31;40m%s\n\u001B[0m", err.Error()))
			return
		}
		log.Println(s.User(), "from keys connect hostid:", hostInfo.Id, "ip:", hostInfo.Addr)
	}

	session, err := conn.NewSession()
	if err != nil {
		log.Println("can not new ssh session ", err)
		return
	}
	defer session.Close()

	modes := cryptoSSH.TerminalModes{
		cryptoSSH.ECHO:          1,
		cryptoSSH.TTY_OP_ISPEED: 14400,
		cryptoSSH.TTY_OP_OSPEED: 14400,
	}

	session.RequestPty("xterm", ptyReq.Window.Height, ptyReq.Window.Width, modes)

	go func() {
		for win := range winCh {
			session.WindowChange(win.Height, win.Width)
		}
	}()

	sessionLogName := fmt.Sprintf("%s/ssh_linux_session_%s_%s_%v.log", SessionDir, s.User(), strings.Split(hostInfo.Addr, ":")[0], time.Now().Unix())
	log.Println(s.User(), "connect hosts:", hostInfo.Id, "ip:", hostInfo.Addr, "session log filename:", sessionLogName)
	f, err := os.Create(sessionLogName)
	if err != nil {
		log.Println("can not open session log", err)
		return
	}

	defer f.Close()

	db.UpdateUserLastHostID(s.User(), hostInfo.Id)

	session.Stdin = s
	outReader, _ := session.StdoutPipe()
	errReader, _ := session.StderrPipe()

	session.Shell()

	go func(reader io.Reader) {
		for {
			buf := make([]byte, 102400)
			n, err := reader.Read(buf)
			if err != nil {
				if err == io.EOF {
					break
				}
				log.Println("read error ", err)
			}
			s.Write(buf[:n])
			f.Write(buf[:n])
		}
	}(errReader)
	go func(reader io.Reader) {
		for {
			buf := make([]byte, 102400)
			n, err := reader.Read(buf)
			if err != nil {
				if err == io.EOF {
					break
				}
				log.Println("read error ", err)
			}
			s.Write(buf[:n])
			f.Write(buf[:n])
		}
	}(outReader)

	session.Wait()
	log.Println(s.User(), "close Connection", hostInfo.Id, "ip:", hostInfo.Addr, "session log filename:", sessionLogName)
}
